import Shadow from "./Shadow";

export default class DBShadowModel extends cc.Component {
    private static _instance: DBShadowModel;
    private shadowData: { [uuid: string]: { node: cc.Node, parent: cc.Node, shadowLife: number, life: number, curCd: number, cd: number, startOpacity: number, opacitySpeed: number, shadows?: Shadow[] } };
    private _intervalID: number;
    private _shadowPool: Shadow[];
    private _prevTime: number;
    private _cameraNode: cc.Node;
    private _camera: cc.Camera;
    private CAMERA_ORTHO_SIZE_OFFSET: number = 200;//摄像机误差大小，出现裁剪问题设置这个

    public static get instance() {
        if (!DBShadowModel._instance) {
            DBShadowModel._instance = new DBShadowModel();
        }
        return DBShadowModel._instance;
    }

    /**
     * 创建残影 
     * @param node 目标
     * @param life 多长时间后停止创建残影(s)
     * @param shadowLife 残影生命(s)
     * @param parent 残影父节点
     * @param startOpacity 残影开始不透明度
     * @param opacitySpeed 残影不透明度衰减速度
     * @param cd 创建残影间隔
    */
    public makeShadow(node: cc.Node, life: number, shadowLife: number, parent: cc.Node, startOpacity: number = 255, opacitySpeed: number = -255, cd: number = 0.1) {
        if (!node || !node.isValid || !node.parent) return;
        if (!this.shadowData) {
            this.shadowData = {};
        }
        if (!this.shadowData[node.uuid]) {
            this.shadowData[node.uuid] = { node: node, parent: parent, life: life, shadowLife: shadowLife, curCd: cd, cd: cd, startOpacity: startOpacity, opacitySpeed: opacitySpeed };
        } else {
            this.shadowData[node.uuid].parent = parent;
            this.shadowData[node.uuid].life = life;
            this.shadowData[node.uuid].shadowLife = shadowLife;
            this.shadowData[node.uuid].curCd = cd;
            this.shadowData[node.uuid].cd = cd;
            this.shadowData[node.uuid].startOpacity = startOpacity;
            this.shadowData[node.uuid].opacitySpeed = opacitySpeed;
        }
        this._initCamera();
        this._start();
        this._crateOneShadow(node.uuid);
    }

    private _initCamera() {
        if (!this.shadowData) return;
        let cameraParent = cc.find('Canvas')
        if (!this._cameraNode) {
            this._cameraNode = new cc.Node;
            cameraParent.addChild(this._cameraNode);
        }
        if (!this._camera) {
            this._camera = this._cameraNode.addComponent(cc.Camera);
            this._camera.depth = 0;
            this._camera.clearFlags = 0;
            this._camera.clearFlags = cc.Camera.ClearFlags.DEPTH | cc.Camera.ClearFlags.STENCIL;
            this._camera.alignWithScreen = false;
            this._camera.enabled = false;
        }
    }

    private _crateOneShadow(uuid: string) {
        if (!this.shadowData || !this.shadowData[uuid] || this.shadowData[uuid].life <= 0) return;

        let data = this.shadowData[uuid];
        let playerNode = this.shadowData[uuid].node;
        data.shadows || (data.shadows = []);
        let shadow: Shadow;
        if (this._shadowPool && this._shadowPool.length > 0) {
            shadow = this._shadowPool.pop();
        } else {
            shadow = new Shadow();
        }

        let centerPos = cc.v2(playerNode.width * playerNode.scaleX * 0.5 - playerNode.width * playerNode.scaleX * playerNode.anchorX, playerNode.height * playerNode.scaleY * 0.5 - playerNode.height * playerNode.scaleY * playerNode.anchorY);
        shadow.init(data.shadowLife, data.startOpacity, data.opacitySpeed);
        shadow.setPosition(data.parent.convertToNodeSpaceAR(playerNode.convertToWorldSpaceAR(centerPos)));
        let cameraOrthoSize = Math.max(Math.abs(playerNode.height * playerNode.scaleY), Math.abs(playerNode.width * playerNode.scaleX)) / 2 + this.CAMERA_ORTHO_SIZE_OFFSET;
        shadow.renderTexture.initWithSize(cameraOrthoSize * 2, cameraOrthoSize * 2);
        centerPos = this._cameraNode.parent.convertToNodeSpaceAR(playerNode.convertToWorldSpaceAR(centerPos))
        this._cameraNode.setPosition(centerPos.x, centerPos.y);
        this._camera.cullingMask = 0x00000000;
        this._camera.cullingMask |= playerNode['_cullingMask'];
        this._camera.orthoSize = cameraOrthoSize;
        this._camera.enabled = true;
        this._camera.targetTexture = shadow.renderTexture;
        this._camera.render(playerNode);
        this._camera.enabled = false;
        if (shadow.parent != data.parent) {
            shadow.parent = data.parent;
        }
        if (!data.shadows.includes(shadow)) {
            data.shadows.push(shadow);
        }
    }

    private _realRecoverOneShadow(uuid: string, shadow: Shadow) {
        let shadows = this.shadowData[uuid].shadows;
        shadow.recover();
        if (shadows.includes(shadow)) {
            shadows.splice(shadows.indexOf(shadow), 1);
        } else {
            console.error("error _realRecoverOneShadow shadows.includes(shadow)")
        }
        if (!this._shadowPool) this._shadowPool = [];
        if (!this._shadowPool.includes(shadow)) {
            this._shadowPool.push(shadow);
        } else {
            console.error("repeat recover shadow")
        }
    }

    private _recoverOneShadow(uuid: string, shadow?: Shadow) {
        if (!this.shadowData || !this.shadowData[uuid]
            || !this.shadowData[uuid].shadows || this.shadowData[uuid].shadows.length <= 0) return;
        if (shadow) {
            this._realRecoverOneShadow(uuid, shadow);
        } else {
            let uuid2Shadows = this.shadowData[uuid].shadows;
            for (var i = 0; i < uuid2Shadows.length; i++) {
                this._realRecoverOneShadow(uuid, uuid2Shadows[i]);
                i--;
            }
        }
    }

    public recoverAllShadow() {
        if (!this.shadowData) return;
        let datas = this.shadowData;
        for (var uuid in datas) {
            this._recoverOneShadow(uuid);
        }
        this._allStop();
    }

    // public destroyAllShadow() {
    //     this.shadowData = null;
    //      this._allStop();
    // }

    private _start() {
        if (!this._intervalID) {
            this._prevTime = Date.now();
            this._intervalID = setInterval(this._loop.bind(this), 1 / 60);
        }
    }

    private _allStop() {
        if (this._intervalID) {
            clearInterval(this._intervalID);
            this._intervalID = null;
        }
        if (this._camera) {
            this._camera.enabled = false;
        }
        this.shadowData = null;
    }

    private _isAllComplete() {
        if (this.shadowData) {
            for (var k in this.shadowData) {
                if (this.shadowData[k].life > 0) {
                    return false;
                }
                let shadows = this.shadowData[k].shadows;
                if (shadows) {
                    for (var i = 0; i < shadows.length; i++) {
                        if (!shadows[i].isOver) {
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }

    private _loop() {
        if (this._isAllComplete()) {
            this._allStop();
            return;
        }
        let dt = Math.floor(((Date.now() - this._prevTime) / 1000) * 10000) / 10000;
        let datas = this.shadowData;
        for (var uuid in datas) {
            let data = datas[uuid];
            if (data.life > 0) {
                data.life -= dt;
            }
            if (data.curCd > 0 && data.life > 0) {
                data.curCd -= dt;
                if (data.curCd <= 0) {
                    data.curCd = data.cd;
                    this._crateOneShadow(uuid);
                }
            }
            let shadows = data.shadows;
            if (shadows) {
                for (var i = 0; i < shadows.length; i++) {
                    shadows[i].update(dt);
                    if (shadows[i].isOver) {
                        this._recoverOneShadow(uuid, shadows[i]);
                        i--;
                    }
                }
            }
        }
        this._prevTime = Date.now();
    }


}
